﻿using System;
using System.Configuration;
using System.Reflection;
using PSE.Framework.Threading;
using PSE.ProcessManager.Common.Process.Exceptions;
using PSE.ProcessManager.Common.Process;

namespace PSE.ProcessManager.Server.Process.Executor
{
    /// <summary>
    /// Classe utilizada para carregar um processo
    /// </summary>
    [Serializable]
    internal class ProcessLoader
    {
        //public CrossProcessManualResetEvent WaitToExit { get; set; }
        const string _processFolderPathKey = "FolderProcessPath";
        string _processFolderPath;
        string _processFullPath;

        /// <summary>
        /// Construtor da classe
        /// </summary>
        public ProcessLoader()
        {
            _processFolderPath = System.Configuration.ConfigurationManager.AppSettings[_processFolderPathKey];
            //this.WaitToExit = new CrossProcessManualResetEvent(false);
        }

        /// <summary>
        /// Inicia um determinado processo.
        /// </summary>
        /// <param name="processId">Id do processo</param>
        public bool Initialize(string assemblyPath, string classFullName, string[] args)
        {
            //while (!System.Diagnostics.Debugger.IsAttached)
            //    System.Threading.Thread.Sleep(1000);

            BaseProcess process = LoadProcess(assemblyPath, classFullName);
            process.Finalized += new EventHandler(process_OnFinalize);
            process.Initialize(args);

            return !process.IsFailed;
        }

        /// <summary>
        /// Metodo que trata o disparo do evento OnFinalize
        /// </summary>
        void process_OnFinalize(object sender, EventArgs e)
        {
            try
            {
                if (ExecutorProcessManager.GetInstance().IsInitialized)
                    ExecutorProcessManager.GetInstance().Exit();
                
                //WaitToExit.Set();
            }
            catch (Exception ex)
            {
                throw new FinalizeException("Process '" + _processFolderPath + "' couldn't be finalized", ex);
            }
        }

        /// <summary>
        /// Carrega a assembly e classe do Processo
        /// </summary>
        /// <param name="processConfiguration">Configurações do Processo</param>
        /// <returns>Processo</returns>
        BaseProcess LoadProcess(string assemblyPath, string classFullName)
        {
            AppDomainSetup info = null;
            AppDomain appDomain = null;
            object processObject = null;
            string processClass;

            try
            {
                _processFullPath = RetrieveProcessFullPath(assemblyPath);
            }
            catch (Exception ex)
            {
                throw new PathRetrieveException("Couldn't retrieve process path '" + assemblyPath + "'", ex);
            }

            try
            {
                // Obtém o tipo da Janela
                processClass = classFullName;
            }
            catch (Exception ex)
            {
                throw new ClassRetrieveException("Couldn't retrieve process class name '" + assemblyPath + "', '" + classFullName + "'", ex);
            }

            //Seta o arquivo de configuração que será vinculado ao Assembly
            info = new AppDomainSetup();
            info.ConfigurationFile = _processFullPath + ".config";

            try
            {
                //Inicializa uma nova instância do processo.
                appDomain = AppDomain.CreateDomain(processClass, null, info);
            }
            catch (Exception ex)
            {
                throw new DomainCreationException("An error ocurred while trying to create a domain for '" + processClass + "'", ex);
            }

            try
            {
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(appDomain_AssemblyResolve);
                appDomain.AssemblyResolve += new ResolveEventHandler(appDomain_AssemblyResolve);
                //Cria instancia da classe contida no AppDomain
                processObject = appDomain.CreateInstanceFromAndUnwrap(_processFullPath, processClass);
            }
            catch (Exception ex)
            {
                throw new CreateInstanceException("An error ocurred while trying to create an instance for '" + assemblyPath + "', '" + processClass + "'", ex);
            }

            BaseProcess process = processObject as BaseProcess;

            if (process == null)
            {
                throw new BaseProcessException("The type '" + processClass + "' must derive from BaseProcess");
            }

            return process;
        }

        Assembly appDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (assembly.GetName().Name == args.Name ||
                    assembly.FullName == args.Name)
                {
                    return assembly;
                }
            }

            //Caso não tenha encontrado nos assemblies carregados, carrega o 
            //assembly pelo arquivo.
            return Assembly.LoadFrom(_processFullPath);
        }


        /// <summary>
        /// Retorna o somente o nome do Assembly caso o mesmo possua uma pasta pre-definida
        /// </summary>
        /// <param name="databaseAssemblyName"></param>
        /// <returns></returns>
        string RepairAssemblyName(string databaseAssemblyName)
        {
            if (databaseAssemblyName.Contains(@"\"))
            {
                string[] assemblyParts = databaseAssemblyName.Split('\\');
                return assemblyParts[assemblyParts.Length - 1];
            }
            else
                return databaseAssemblyName;
        }

        /// <summary>
        /// Retorna o caminho completo do assembly do processo.
        /// </summary>
        /// <param name="processConfiguration"></param>
        /// <returns></returns>
        string RetrieveProcessFullPath(string assemblyPath)
        {
            string processPath = _processFolderPath;

            if (!processPath.EndsWith(@"\"))
                processPath += @"\";

            processPath += assemblyPath;

            if (!processPath.ToLower().EndsWith(".dll"))
                processPath += ".dll";

            return processPath;
        }
    }
}